package com.xuecheng.orders.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.messagesdk.model.po.MqMessage;
import com.xuecheng.messagesdk.service.MqMessageService;
import com.xuecheng.orders.config.AlipayConfig;
import com.xuecheng.orders.config.PayNotifyConfig;
import com.xuecheng.orders.mapper.XcOrdersGoodsMapper;
import com.xuecheng.orders.mapper.XcOrdersMapper;
import com.xuecheng.orders.mapper.XcPayRecordMapper;
import com.xuecheng.orders.model.dto.AddOrderDto;
import com.xuecheng.orders.model.dto.PayRecordDto;
import com.xuecheng.orders.model.dto.PayStatusDto;
import com.xuecheng.orders.model.po.XcOrders;
import com.xuecheng.orders.model.po.XcOrdersGoods;
import com.xuecheng.orders.model.po.XcPayRecord;
import com.xuecheng.orders.service.OrderService;
import com.zmt.xuechengplusbase.exception.xuechengplusException;
import com.zmt.xuechengplusbase.utils.IdWorkerUtils;
import com.zmt.xuechengplusbase.utils.QRCodeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private XcOrdersMapper xcOrdersMapper;

    @Autowired
    private XcOrdersGoodsMapper xcOrdersGoodsMapper;

    @Autowired
    private XcPayRecordMapper xcPayRecordMapper;

    @Autowired
    private OrderServiceImpl orderServiceImpl;

    @Autowired
    private MqMessageService mqMessageService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Value("${pay.qrcodeurl}")
    private String payUrl;

    @Value("${pay.alipay.APP_ID}")
    private String APP_ID;

    @Value("${pay.alipay.APP_PRIVATE_KEY}")
    private String APP_PRIVATE_KEY;

    @Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")
    private String ALIPAY_PUBLIC_KEY;

    /**
     * 生成订单
     *
     * @param addOrderDto
     * @param userId
     * @return
     */
    @Override
    @Transactional
    public PayRecordDto createOrder(String userId, AddOrderDto addOrderDto) {
        //首先要生成一个订单号
        XcOrders xcOrders = saveOrders(userId, addOrderDto);
        //其次生成一个支付记录，将支付记录的id传给支付宝用于记录是否重复付款
        XcPayRecord payRecode = createPayRecode(xcOrders);
        //生成二维码
        Long payNo = payRecode.getPayNo();
        String url = String.format(payUrl, payNo);

        String qrCode = getQrCode(url);
        //构造返回参数
        PayRecordDto payRecordDto = new PayRecordDto();
        BeanUtils.copyProperties(payRecode, payRecordDto);
        payRecordDto.setQrcode(qrCode);
        return payRecordDto;
    }

    /**
     * 测试扫码支付
     *
     * @param payNo
     */
    @Override
    public String requestPay(String payNo) {
        XcPayRecord payRecodeByPayNo = getPayRecodeByPayNo(payNo);
        if (payRecodeByPayNo == null) {
            xuechengplusException.cast("不存在该支付记录");
        }
        if ("601002".equals(payRecodeByPayNo.getStatus())) {
            xuechengplusException.cast("该订单已经支付");
        }
        AlipayClient client = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE);//获得初始化的AlipayClient
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的request
//        alipayRequest.setReturnUrl("http://domain.com/CallBack/return_url.jsp");
//        alipayRequest.setNotifyUrl("http://tjxt-user-t.itheima.net/xuecheng/orders/paynotify");//在公共参数中设置回跳和通知地址
        alipayRequest.setBizContent("{" +
                " \"out_trade_no\":\"" + payRecodeByPayNo.getPayNo() + "\"," +
                " \"total_amount\":\"" + payRecodeByPayNo.getTotalPrice() + "\"," +
                " \"subject\":\"" + payRecodeByPayNo.getOrderName() + "\"," +
                " \"product_code\":\"QUICK_WAP_PAY\"" +
                " }");//填充业务参数
        String form = "";
        try {
            //请求支付宝下单接口,发起http请求
            form = client.pageExecute(alipayRequest).getBody(); //调用SDK生成表单//form里面时js代码
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return form;
    }

    /**
     * 生成验证码
     *
     * @param url
     * @return
     */
    public String getQrCode(String url) {
        String qrCode = null;
        QRCodeUtil qrCodeUtil = new QRCodeUtil();
        try {
            qrCode = qrCodeUtil.createQRCode(url, 200, 200);
        } catch (IOException e) {
            xuechengplusException.cast("生成二维码失败");
        }
        return qrCode;
    }


    /**
     * 根据订单表添加支付记录
     *
     * @param xcOrders
     * @return
     */
    public XcPayRecord createPayRecode(XcOrders xcOrders) {
        //似乎是可以重复插入的，只要pay_no与out_pay_no不重复即可
        if (xcOrders == null) {
            xuechengplusException.cast("订单不存在");
        }
        if ("600002".equals(xcOrders.getStatus())) {
            xuechengplusException.cast("不要重复支付");
        }
        XcPayRecord xcPayRecord = new XcPayRecord();
        xcPayRecord.setPayNo(IdWorkerUtils.getInstance().nextId());
        xcPayRecord.setOrderId(xcOrders.getId());//商品订单号
        xcPayRecord.setOrderName(xcOrders.getOrderName());
        xcPayRecord.setTotalPrice(xcOrders.getTotalPrice());
        xcPayRecord.setCurrency("CNY");
        xcPayRecord.setCreateDate(LocalDateTime.now());
        xcPayRecord.setStatus("601001");//未支付
        xcPayRecord.setUserId(xcOrders.getUserId());
        int insert = xcPayRecordMapper.insert(xcPayRecord);
        if (insert < 0) {
            xuechengplusException.cast("插入支付记录失败");
        }
        return xcPayRecord;
    }

    /**
     * 保存订单表
     *
     * @param userId
     * @param addOrderDto
     * @return
     */
    public XcOrders saveOrders(String userId, AddOrderDto addOrderDto) {
        String outBusinessId = addOrderDto.getOutBusinessId();
        XcOrders xcOrders = getXcOrderByOutBusinessId(outBusinessId);
        if (xcOrders != null) {
            return xcOrders;
        }
        long id = IdWorkerUtils.getInstance().nextId();
        xcOrders = new XcOrders();
        xcOrders.setId(id);
        xcOrders.setTotalPrice(addOrderDto.getTotalPrice());
        xcOrders.setCreateDate(LocalDateTime.now());
        xcOrders.setStatus("600001");//未支付
        xcOrders.setUserId(userId);
        xcOrders.setOrderType(addOrderDto.getOrderType());
        xcOrders.setOrderName(addOrderDto.getOrderName());
        xcOrders.setOrderDetail(addOrderDto.getOrderDetail());
        xcOrders.setOrderDescrip(addOrderDto.getOrderDescrip());
        xcOrders.setOutBusinessId(addOrderDto.getOutBusinessId());//选课记录id
        int insert = xcOrdersMapper.insert(xcOrders);
        if (insert < 0) {
            xuechengplusException.cast("插入订单表失败");
        }
        //将前端传入的json传转为对象保存订单明表
        //保存订单明细表
        String orderDetail = addOrderDto.getOrderDetail();
        List<XcOrdersGoods> xcOrdersGoods = JSON.parseArray(orderDetail, XcOrdersGoods.class);
        xcOrdersGoods.forEach(goods -> {
            goods.setOrderId(id);
            //TODO 准备修改XML文件批量添加
            xcOrdersGoodsMapper.insert(goods);
        });
        return xcOrders;
    }

    /**
     * 根据业务id进行查询订单
     *
     * @param id 实际上是选课表主键
     * @return
     */
    public XcOrders getXcOrderByOutBusinessId(String id) {
        XcOrders xcOrders = xcOrdersMapper.selectOne(new LambdaQueryWrapper<XcOrders>().eq(XcOrders::getOutBusinessId, id));
        return xcOrders;
    }

    /**
     * 根据支付记录号查询支付记录
     *
     * @param payNo
     * @return
     */
    public XcPayRecord getPayRecodeByPayNo(String payNo) {
        XcPayRecord xcPayRecord = xcPayRecordMapper.selectOne(new LambdaQueryWrapper<XcPayRecord>().eq(XcPayRecord::getPayNo, payNo));
        return xcPayRecord;
    }

    /**
     * Service层调用的查询支付结果
     *
     * @param payNo
     * @return
     */
    @Override
    public PayRecordDto getPayResult(String userId, String payNo) {
        //查询结果
        PayStatusDto result = getResult(payNo);
        //向数据库中更新信息
        orderServiceImpl.savePayResult(userId, result);
        //构造返回结果
        XcPayRecord payRecodeByPayNo = getPayRecodeByPayNo(payNo);
        PayRecordDto payRecordDto = new PayRecordDto();
        BeanUtils.copyProperties(payRecodeByPayNo, payRecordDto);
        return payRecordDto;
    }

    /**
     * 更新支付结果的状态
     * <p>
     * 这里没有必要使用userId来进行判断是否重复支付，因为在生成二维码支付那部分已经进行了是否已经进行了支付判断，如果已经支付不会产生新的二维码
     *
     * @param result
     */
    @Transactional
    public void savePayResult(String userId, PayStatusDto result) {
        //更新支付记录表状态
        String out_trade_no = result.getOut_trade_no();//相当与拿到的payNO
        XcPayRecord payRecodeByPayNo = getPayRecodeByPayNo(out_trade_no);
        if (payRecodeByPayNo == null) {
            //说明就没有这个订单
            xuechengplusException.cast("不存在该订单信息");
        }
        Long orderId = payRecodeByPayNo.getOrderId();
        //在这里加一个判断订单id为orderId且Status为已支付的查询
        LambdaQueryWrapper<XcPayRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(XcPayRecord::getOrderId, orderId).eq(XcPayRecord::getStatus, "601002").eq(XcPayRecord::getUserId, userId);
        XcPayRecord xcPayRecord = xcPayRecordMapper.selectOne(wrapper);
        if (xcPayRecord != null) {
            //说明之前下过这个订单,不需要再次付款。
            xuechengplusException.cast("已经购买过本课程");
            return;
        }
        XcOrders xcOrders = xcOrdersMapper.selectById(orderId);
        if (xcOrders == null) {
            xuechengplusException.cast("没有相关订单信息");
        }
        //经过上面的判断，这里还需要吗？(不需要了)
        if (!payRecodeByPayNo.getStatus().equals("601001")) {
            //说明支付过了，不用走下面的了
            return;
        }
        //以上操作都在对数据库信息的核验，并未涉及到支付宝返回信息

        //开始对支付宝信息进操作
        if (result.getTrade_status().equals("TRADE_SUCCESS")) {
            //说明支付成功，进行更改
            payRecodeByPayNo.setStatus("601002");
            payRecodeByPayNo.setOutPayNo(result.getTrade_no());
            payRecodeByPayNo.setOutPayChannel("AliPay");
            payRecodeByPayNo.setPaySuccessTime(LocalDateTime.now());
            xcPayRecordMapper.updateById(payRecodeByPayNo);
        } else {
            //说明失败，直接返回
            return;
        }

        //对订单表进行更改
        xcOrders.setStatus("600002");
        xcOrdersMapper.updateById(xcOrders);


        //保存消息记录,参数1：支付结果通知类型，2: 业务id，3:业务类型
        MqMessage mqMessage = mqMessageService.addMessage("payresult_notify", xcOrders.getOutBusinessId(), xcOrders.getOrderType(), null);
        //通知消息
        notifyResult(mqMessage);
    }

    @Override
    public void notifyResult(MqMessage mqMessage) {
        //1、消息体，转json
        String msg = JSON.toJSONString(mqMessage);
        //设置消息持久化
        Message msgObj = MessageBuilder.withBody(msg.getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();
        // 2.全局唯一的消息ID，需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(mqMessage.getId().toString());
        // 3.添加callback
        correlationData.getFuture().addCallback(
                result -> {
                    if (result.isAck()) {
                        // 3.1.ack，消息成功
                        log.debug("通知支付结果消息发送成功, ID:{}", correlationData.getId());
                        //删除消息表中的记录
                        mqMessageService.completed(mqMessage.getId());
                    } else {
                        // 3.2.nack，消息失败
                        log.error("通知支付结果消息发送失败, ID:{}, 原因{}", correlationData.getId(), result.getReason());
                    }
                },
                ex -> log.error("消息发送异常, ID:{}, 原因{}", correlationData.getId(), ex.getMessage())
        );
        // 发送消息
        rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT, "", msgObj, correlationData);
    }

    /**
     * 查询支付结果
     *
     * @param payNo
     * @return
     */
    public PayStatusDto getResult(String payNo) {
        //向查询支付宝查询
        AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE);
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("out_trade_no", payNo);
        request.setBizContent(jsonObject.toString());
        AlipayTradeQueryResponse response = null;
        try {
            response = alipayClient.execute(request);
            if (!response.isSuccess()) {
                xuechengplusException.cast("请求支付宝查询支付结果失败");
            }
        } catch (AlipayApiException e) {
            xuechengplusException.cast("支付宝查询失败");
            e.printStackTrace();
        }
        String body = response.getBody();
        //将支付宝返回结果转为Map
        Map result = JSON.parseObject(body, Map.class);
        Map<String, String> alipay_trade_query_response = (Map) result.get("alipay_trade_query_response");

        PayStatusDto payStatusDto = new PayStatusDto();

        payStatusDto.setOut_trade_no(payNo);
        payStatusDto.setTrade_no(alipay_trade_query_response.get("trade_no"));
        payStatusDto.setTrade_status(alipay_trade_query_response.get("trade_status"));
        payStatusDto.setApp_id(APP_ID);
        payStatusDto.setTotal_amount(alipay_trade_query_response.get("total_amount"));
        return payStatusDto;
    }
}
